import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

/**
 * A simple predator-prey simulator, based on a rectangular field
 * containing rabbits and foxes.
 * 
 * @author David J. Barnes and Michael Kolling
 * @version 2008.03.30
 */
public class Simulator implements Runnable
{
    // Constants representing configuration information for the simulation.
    // The default width for the grid.
    private static final int DEFAULT_WIDTH = 100;
    // The default depth of the grid.
    private static final int DEFAULT_DEPTH = 100;
    // The probability that a fox will be created in any given grid position.
    private static final double FOX_CREATION_PROBABILITY = 0.02;
    // The probability that a rabbit will be created in any given grid position.
    private static final double RABBIT_CREATION_PROBABILITY = 0.08; 
    // The probability that a hunter will be created in any given grid position.
    private static final double HUNTER_CREATION_PROBABILITY = 0.06;
    // The probability that a sheriff will be created in any given grid position.
    private static final double SHERIFF_CREATION_PROBABILITY = 0.0005;
    // The probability that a grass will be created in any given grid position.
    private static final double GRASS_CREATION_PROBABILITY = 0.04;
    private static double waitForFrame = 1000;

    // List of static environments in field;
    private List<StaticEnvironment> staticEnvironments;

    // List of animals in the field.
    private List<Actor> actors;
    // The current state of the field.
    private Field field;
    // The current step of the simulation.
    private int step;
    // The step the simulation was at when the start/simulate X button was clicked
    private int startStep = -1;
    // The amount of steps the simulation has to do (when the X steps button was clicked)
    private int amountSteps = 0;
    // A graphical view of the simulation.
    private View view;
    // If the simulator is running or not
    private boolean running;
    // Check for play/stop button
    private boolean run;
    
    private Controller controller;
    /**
     * Construct a simulation field with default size.
     */
    public Simulator()
    {
       this(DEFAULT_DEPTH, DEFAULT_WIDTH, getWaitForFrame());
    }
    
    /**
     * Create a simulation field with the given size.
     * @param depth Depth of the field. Must be greater than zero.
     * @param width Width of the field. Must be greater than zero.
     */
    public Simulator(int depth, int width, double waitForFrame)
    {
        if(width <= 0 || depth <= 0) {
            System.out.println("The dimensions must be greater than zero.");
            System.out.println("Using default values.");
            depth = DEFAULT_DEPTH;
            width = DEFAULT_WIDTH;
        }
        
        actors = new ArrayList<Actor>();
        field = new Field(depth, width);
        staticEnvironments = new ArrayList<StaticEnvironment>();

        // Create a view of the state of each location in the field.
        view = new View(depth, width);
        view.setColor(Rabbit.class, Color.orange);
        view.setColor(Fox.class, Color.blue);
        view.setColor(Grass.class, new Color(0, 75, 21));
        view.setColor(Hunter.class, Color.black);
        view.setColor(Sheriff.class, new Color(255, 0 , 0));
        controller = new Controller(view, this);
        controller.buttonEvents();
        // Setup a valid starting point.
        reset();
    }
    
    /**
     * Run the simulation from its current state for the given number of steps.
     * Stop before the given number of steps if it ceases to be viable.
     * @param numSteps The number of steps to run for.
     */
    public void simulate(int numSteps)
    {
    	this.startStep = this.step;
        this.amountSteps = numSteps;
    	this.start();
    	this.startStep = -1;
    	this.amountSteps = 0;
    }
    
    /**
     * Run the simulation from its current state for a single step.
     * Iterate over the whole field updating the state of each
     * fox and rabbit.
     */
    public void simulateOneStep()
    {
        step++;

        // Provide space for newborn animals.
        List<Actor> newActors = new ArrayList<Actor>();        
        // Let all rabbits act.
        for(Iterator<Actor> it = actors.iterator(); it.hasNext(); ) {
            Actor actor = it.next();
            actor.act(newActors);
            if(!actor.isActive()) {
                it.remove();
            }
        }
               
        // Add the newly born foxes and rabbits to the main lists.
        actors.addAll(newActors);

        view.showStatus(step, field);
    }
        
    /**
     * Reset the simulation to a starting position.
     */
    public void reset()
    {
        step = 0;
        actors.clear();
        populate();
        
        // Show the starting state in the view.
        view.showStatus(step, field);
    }
    
    /**
     * Randomly populate the field with foxes and rabbits.
     */
    private void populate()
    {
        Random rand = Randomizer.getRandom();
        field.clear();
        for(int row = 0; row < field.getDepth(); row++) {
            for(int col = 0; col < field.getWidth(); col++) {
                if(rand.nextDouble() <= FOX_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    Fox fox = new Fox(true, field, location);
                    actors.add(fox);
                }
                else if(rand.nextDouble() <= RABBIT_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    Rabbit rabbit = new Rabbit(true, field, location);
                    actors.add(rabbit);
                }
                else if(rand.nextDouble() <= HUNTER_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    Hunter hunter = new Hunter(true, field, location);
                    actors.add(hunter);
                }
                else if(rand.nextDouble() <= GRASS_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    boolean isInfected = new Random().nextInt(10) < 8 ? true : false;
                    Grass grass = new Grass(true, field, location, isInfected);
                    staticEnvironments.add(grass);
                }
                else if(rand.nextDouble() <= SHERIFF_CREATION_PROBABILITY) {
                    Location location = new Location(row, col);
                    //boolean isInfected = new Random().nextInt(10) < 8 ? true : false;
                    Sheriff sheriff = new Sheriff(true, field, location);
                    actors.add(sheriff);
                }
                // else leave the location empty.
            }
        }
    }

	public static double getWaitForFrame() {
		return waitForFrame;
	}

	public static void setWaitForFrame(double waitForFrame) {
		Simulator.waitForFrame = waitForFrame;
	}
	
	public void start(){
        new Thread(this).start();
    }

    public void run(){
        if(running == true)
            return;
        run = true;
        while(run && view.isViable(field) && needsToRun()){
            running = true;
            simulateOneStep();
            running = false;
        }
    }
    
    public void stop () {
        run = false;
        while(running){
            try {
                Thread.sleep(1);
            } catch (InterruptedException ex) {}
        }
    }
    
    public boolean needsToRun() {
    	if (this.startStep >= 0 && this.amountSteps >= 0) {
    		if (this.step < (this.startStep + this.amountSteps)) {
    			return true;
    		}
    		else {
    			return false;
    		}
    	}
    	else {
    		return true;
    	}
    }
}

